package com.org.devg.monitor.client.aspect;

import com.alibaba.fastjson.JSONObject;
import com.org.devg.monitor.client.MonitorClientAutoConfiguration;
import com.org.devg.monitor.client.MonitorClientInit;
import com.org.devg.monitor.client.anno.MonitorController;
import com.org.devg.monitor.client.anno.MonitorMapper;
import com.org.devg.monitor.client.anno.MonitorService;
import com.org.devg.monitor.client.model.BasicParams;
import com.org.devg.monitor.client.model.DevgRabbitMessage;
import com.org.devg.monitor.client.model.MonitorClientConfig;
import com.org.devg.monitor.client.utils.IpAddressUtil;
import com.org.devg.monitor.client.utils.MonitorUtil;
import com.org.devg.monitor.common.Common;
import com.org.devg.monitor.common.CommonEnum;
import com.org.devg.monitor.common.MonitorMessage;
import com.rabbitmq.client.Channel;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

@Aspect
public class MonitorWebAspect implements Ordered {

    private final Logger logger = LoggerFactory.getLogger(MonitorWebAspect.class);

    public static final AtomicLong s0MessageNumber = new AtomicLong(0);

    public static final AtomicLong s1MessageNumber = new AtomicLong(0);

    public static Long nodeNumber = 0L;

    public static Boolean showStackTraceMsg = false;

    private MonitorClientConfig monitorClientConfig;

    private List<String> nonClassMethodlist = null;

    public MonitorWebAspect(MonitorClientConfig monitorClientConfig) {
        this.monitorClientConfig = monitorClientConfig;
        if (null != monitorClientConfig && null != monitorClientConfig.getMonitorNonClassMethod()
                && monitorClientConfig.getMonitorNonClassMethod().length > 0) {
            nonClassMethodlist = Arrays.asList(monitorClientConfig.getMonitorNonClassMethod());
        }
    }

    @Pointcut("execution(* *..*Controller.*(..))")
    public void monitorController() {
    }

    @Pointcut("execution(* *..*Service.*(..))")
    public void monitorService() {
    }

    @Pointcut("execution(* *..*Mapper.*(..))")
    public void monitorMappper() {
    }

    @Around(value = "monitorController()")
    public Object monitorController(ProceedingJoinPoint joinPoint) throws Throwable {
        return controllerObject(joinPoint);
    }

    @Around(value = "monitorService()")
    public Object monitorService(ProceedingJoinPoint joinPoint) throws Throwable {
        return serviceObject(joinPoint);
    }

    @Around(value = "monitorMappper()")
    public Object monitorMappper(ProceedingJoinPoint joinPoint) throws Throwable {
        return mapperObject(joinPoint);
    }


    private Object controllerObject(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();//目标方法名
        String clazzName = joinPoint.getSignature().getDeclaringTypeName();//目标方法所属类的类名
        String classMethod = clazzName + "." + methodName;
        if (null != nonClassMethodlist && (nonClassMethodlist.contains(clazzName) || nonClassMethodlist.contains(classMethod))) {
            return joinPoint.proceed();
        }
        MonitorController classRequestController = joinPoint.getTarget().getClass().getAnnotation(MonitorController.class);
        MonitorController methodRequestController = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(MonitorController.class);
        Boolean enableMonitorController = monitorClientConfig.getEnableMonitorController();
        if ((null == enableMonitorController || !enableMonitorController) && methodRequestController == null && classRequestController == null) {
            return joinPoint.proceed();
        }
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String requestUrl = request.getRequestURL().toString();
        String requestMethod = request.getMethod();
        String requestIp = IpAddressUtil.getIp(request);
        /**
         * 生成messageKey
         */
        Object monitorExtMsg = request.getAttribute("monitorExtMsg");
        String localIp = IpAddressUtil.getLocalIp();
        String nowDay = MonitorUtil.localDateFormat(LocalDate.now(), "dd");
        Long msgNumber = getMessageNumber(nowDay);
        Long messageKey = Long.valueOf(nowDay + nodeNumber + msgNumber);
        String requestParams = monitorClientConfig.getRequestParams();
        Map<String, Object> theRequestParams = new HashMap<>(4);
        if (!MonitorUtil.isNullOrEmpty(requestParams)) {
            // 下面两个数组中，参数值和参数名的个数和位置是一一对应的。
            Object[] args = joinPoint.getArgs(); // 参数值
            String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); // 参数名
            if (null != argNames && argNames.length > 0) {
                for (int i = 0; i < argNames.length; i++) {
                    Object object = args[i];
                    if (MonitorUtil.isNullOrEmpty(object)) {
                        continue;
                    }
                    if (object instanceof String || object instanceof Short || object instanceof Long || object instanceof Byte
                            || object instanceof Integer || object instanceof Double || object instanceof Float) {
                        if (requestParams.indexOf(argNames[i]) >= 0) {
                            theRequestParams.put(argNames[i], args[i]);
                        }
                    } else if (object instanceof Map) {
                        Map<String, Object> resultMap = JSONObject.parseObject(JSONObject.toJSONString(object));
                        if (null != resultMap && !resultMap.isEmpty()) {
                            for (String key : resultMap.keySet()) {
                                if (requestParams.indexOf(key) >= 0) {
                                    theRequestParams.put(key, resultMap.get(key));
                                }
                            }
                        }
                    } else if (object instanceof BasicParams) {
                        try {
                            Field[] fields = object.getClass().getDeclaredFields();
                            if (null != fields && fields.length > 0) {
                                for (Field field : fields) {
                                    field.setAccessible(true);
                                    String fieldName = field.getName();
                                    if (requestParams.indexOf(fieldName) >= 0) {
                                        char[] ch = fieldName.toCharArray();
                                        if (ch[0] >= 'a' && ch[0] <= 'z') {
                                            ch[0] = (char) (ch[0] - 32);
                                        }
                                        String theMethodName = "get" + new String(ch);
                                        Method method = object.getClass().getDeclaredMethod(theMethodName, null);
                                        Object resultObject = method.invoke(object, null);
                                        theRequestParams.put(fieldName, resultObject);
                                    }
                                }
                            }
                        } catch (Exception ex) {
                            logger.error("get BasicParams value error", ex);
                        }
                    } else if (object instanceof HttpServletRequest) {
                        HttpServletRequest requestObject = (HttpServletRequest) object;
                        if (null != requestObject) {
                            Map<String, String[]> parameterMap = requestObject.getParameterMap();
                            if (null != parameterMap && !parameterMap.isEmpty()) {
                                String[] requestKeys = requestParams.split(",");
                                for (String requestKey : requestKeys) {
                                    if (!parameterMap.containsKey(requestKey)) {
                                        continue;
                                    }
                                    String[] requestValue = parameterMap.get(requestKey);
                                    if (null != requestValue && requestValue.length > 0) {
                                        theRequestParams.put(requestKey, requestValue[0]);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        String startTime = MonitorUtil.getStartTime();
        long t1 = System.currentTimeMillis();
        try {
            Object object = joinPoint.proceed(); //执行目标方法
            long t2 = System.currentTimeMillis();
            String modelName = monitorClientConfig == null ? null : monitorClientConfig.getModelName();
            long costTime = t2 - t1;
            MonitorMessage monitorMessage = getMonitorMessage(CommonEnum.messageType.MsgController.getKey(), classMethod, requestUrl, requestIp, localIp, costTime,
                    startTime, CommonEnum.resultType.INFO.getKey(), modelName, theRequestParams, messageKey, monitorExtMsg, null);
            fillMonitorRequestMessageKey(request, monitorMessage);
            String infoMsg =
                    "devg-monitor execute messageKey:" + monitorMessage.getMessageKey() + ",method:" + methodName + ",costTime:" + costTime + "ms";
            logger.info(infoMsg);
//            String sendMqMsg = JSON.toJSONString(monitorMessage);
            this.sendMessage(monitorMessage);
            return object;
        } catch (Throwable throwable) {
            long t2 = System.currentTimeMillis();
            String modelName = monitorClientConfig == null ? null : monitorClientConfig.getModelName();
            MonitorMessage monitorMessage = getMonitorMessage(CommonEnum.messageType.MsgController.getKey(), classMethod, requestUrl, requestIp, localIp, (t2 - t1),
                    startTime, CommonEnum.resultType.ERROR.getKey(), modelName, theRequestParams, messageKey, monitorExtMsg, throwable);
            fillMonitorRequestMessageKey(request, monitorMessage);
            String errorMsg = "devg-monitor execute messageKey:" + monitorMessage.getMessageKey()
                    + ",method:" + methodName + ",errorMessage:" + throwable.getMessage();
            logger.error(errorMsg);
//            String sendMqMsg = JSON.toJSONString(monitorMessage);
            this.sendMessage(monitorMessage);
            throw throwable;
        }
    }

    private Object serviceObject(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();//目标方法名
        String clazzName = joinPoint.getSignature().getDeclaringTypeName();//目标方法所属类的类名
        String classMethod = clazzName + "." + methodName;
        MonitorService classRequestService = joinPoint.getTarget().getClass().getAnnotation(MonitorService.class);
        MonitorService methodRequestService = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(MonitorService.class);
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String startTime = MonitorUtil.getStartTime();

        /**
         * 生成messageKey
         */
        String localIp = IpAddressUtil.getLocalIp();
        String nowDay = MonitorUtil.localDateFormat(LocalDate.now(), "dd");
        Long msgNumber = getMessageNumber(nowDay);
        Long messageKey = Long.valueOf(nowDay + nodeNumber + msgNumber);
        long t1 = System.currentTimeMillis();
        try {
            Object object = joinPoint.proceed(); //执行目标方法
            long t2 = System.currentTimeMillis();
            if (null != nonClassMethodlist && (nonClassMethodlist.contains(clazzName) || nonClassMethodlist.contains(classMethod))) {
                return object;
            }
            Boolean enableMonitorService = monitorClientConfig.getEnableMonitorService();
            if ((null == enableMonitorService || !enableMonitorService) && methodRequestService == null && classRequestService == null) {
                return object;
            }
            String modelName = monitorClientConfig.getModelName();
            long costTime = t2 - t1;
            MonitorMessage monitorMessage = getMonitorMessage(CommonEnum.messageType.MsgService.getKey(), classMethod, null, null, localIp, costTime,
                    startTime, CommonEnum.resultType.INFO.getKey(), modelName, null, messageKey, null, null);
            fillMonitorRequestMessageKey(request, monitorMessage);
            String infoMsg =
                    "devg-monitor execute messageKey:" + monitorMessage.getMessageKey() + ",method:" + methodName + ",costTime:" + costTime + "ms";
            logger.info(infoMsg);
//            String sendMqMsg = JSON.toJSONString(monitorMessage);
            this.sendMessage(monitorMessage);
            return object;
        } catch (Throwable throwable) {
            long t2 = System.currentTimeMillis();
            String modelName = monitorClientConfig == null ? null : monitorClientConfig.getModelName();
            MonitorMessage monitorMessage = getMonitorMessage(CommonEnum.messageType.MsgService.getKey(), classMethod, null, null, localIp, (t2 - t1),
                    startTime, CommonEnum.resultType.ERROR.getKey(), modelName, null, messageKey, null, throwable);
            fillMonitorRequestMessageKey(request, monitorMessage);
            String errorMsg = "devg-monitor execute messageKey:" + monitorMessage.getMessageKey()
                    + ",method:" + methodName + ",errorMessage:" + throwable.getMessage();
            logger.error(errorMsg);
//            String sendMqMsg = JSON.toJSONString(monitorMessage);
            this.sendMessage(monitorMessage);
            throw throwable;
        }
    }

    private Object mapperObject(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();//目标方法名
        String clazzName = joinPoint.getSignature().getDeclaringTypeName();//目标方法所属类的类名
        String classMethod = clazzName + "." + methodName;
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        MonitorMapper classRequestMapper = joinPoint.getTarget().getClass().getAnnotation(MonitorMapper.class);
        MonitorMapper methodRequestMapper = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(MonitorMapper.class);
        String startTime = MonitorUtil.getStartTime();
        /**
         * 生成messageKey
         */
        String localIp = IpAddressUtil.getLocalIp();
        String nowDay = MonitorUtil.localDateFormat(LocalDate.now(), "dd");
        Long msgNumber = getMessageNumber(nowDay);
        Long messageKey = Long.valueOf(nowDay + nodeNumber + msgNumber);
        long t1 = System.currentTimeMillis();
        try {
            Object object = joinPoint.proceed(); //执行目标方法
            long t2 = System.currentTimeMillis();
            if (null != nonClassMethodlist && (nonClassMethodlist.contains(clazzName) || nonClassMethodlist.contains(classMethod))) {
                return object;
            }
            Boolean enableMonitorMapper = monitorClientConfig.getEnableMonitorMapper();
            if ((null == enableMonitorMapper || !enableMonitorMapper) && methodRequestMapper == null && classRequestMapper == null) {
                return object;
            }
            String modelName = monitorClientConfig.getModelName();
            long costTime = t2 - t1;
            MonitorMessage monitorMessage = getMonitorMessage(CommonEnum.messageType.MsgMapper.getKey(), classMethod, null, null, localIp, costTime,
                    startTime, CommonEnum.resultType.INFO.getKey(), modelName, null, messageKey, null, null);
            fillMonitorRequestMessageKey(request, monitorMessage);
            String infoMsg =
                    "devg-monitor execute messageKey:" + monitorMessage.getMessageKey() + ",method:" + methodName + ",costTime:" + costTime + "ms";
            logger.info(infoMsg);
//            String sendMqMsg = JSON.toJSONString(monitorMessage);
            this.sendMessage(monitorMessage);
            return object;
        } catch (Throwable throwable) {
            long t2 = System.currentTimeMillis();
            String modelName = monitorClientConfig == null ? null : monitorClientConfig.getModelName();
            MonitorMessage monitorMessage = getMonitorMessage(CommonEnum.messageType.MsgMapper.getKey(), classMethod, null, null, localIp, (t2 - t1),
                    startTime, CommonEnum.resultType.ERROR.getKey(), modelName, null, messageKey, null, throwable);
            fillMonitorRequestMessageKey(request, monitorMessage);
            String errorMsg = "devg-monitor execute messageKey:" + monitorMessage.getMessageKey()
                    + ",method:" + methodName + ",errorMessage:" + throwable.getMessage();
            logger.error(errorMsg);
//            String sendMqMsg = JSON.toJSONString(monitorMessage);
            this.sendMessage(monitorMessage);
            throw throwable;
        }
    }

    private MonitorMessage getMonitorMessage(int MessageType, String classMethod, String requestUrl, String requestIp, String localIp, long costTime, String startTime,
                                             String resultType, String modelName, Map<String, Object> resultParams, Long messageKey, Object monitorExtMsg, Throwable throwable) {
        MonitorMessage monitorMessage = new MonitorMessage();
        monitorMessage.setMessageType(MessageType);
        monitorMessage.setClassMethod(classMethod);
        if (null != requestUrl && !requestUrl.equals("")) {
            monitorMessage.setRequestUrl(requestUrl);
        }
        if (null != requestIp && !requestIp.equals("")) {
            monitorMessage.setRequestIp(requestIp);
        }
        if (null != localIp && !localIp.equals("")) {
            monitorMessage.setLocalIp(localIp);
        }
        monitorMessage.setCostTime(costTime);
        monitorMessage.setStartTime(startTime);
        monitorMessage.setResultType(resultType);
        if (null != modelName && !modelName.equals("")) {
            monitorMessage.setModelName(modelName);
        }
        if (null != resultParams && !resultParams.isEmpty()) {
            monitorMessage.setRequestParams(resultParams);
        }
        monitorMessage.setMessageModelType(MonitorUtil.message);
        if (null != monitorExtMsg) {
            monitorMessage.setMonitorExtMsg(monitorExtMsg.toString());
        }
        monitorMessage.setMessageKey(messageKey);
        if (showStackTraceMsg && null != throwable) {
            monitorMessage.setStackTraceMsg(MonitorUtil.stringifyException(throwable));
        }
        monitorMessage.setEvnName(monitorClientConfig.getEvnName());
        return monitorMessage;
    }

    private void fillMonitorRequestMessageKey(HttpServletRequest request, MonitorMessage monitorMessage) {
        Long monitorRequstMessage;
        Object monitorRequstMessageObject = request.getAttribute("monitorRequestMessageKey");
        if (null == monitorRequstMessageObject) {
            monitorRequstMessage = monitorMessage.getMessageKey();
            request.setAttribute("monitorRequestMessageKey", monitorRequstMessage);
        } else {
            monitorRequstMessage = Long.valueOf(monitorRequstMessageObject.toString());
        }
        monitorMessage.setMonitorRequestMessageKey(monitorRequstMessage);
    }

    public <T> void sendMessage(T message) {
        try {
            Channel channel = MonitorClientInit.connectionChannelMap.get(Common.messageRouteKey);
            if (null != channel) {

                DevgRabbitMessage devgRabbitMessage = new DevgRabbitMessage();
                devgRabbitMessage.setMessage(message);
                devgRabbitMessage.setMessageClass(message.getClass().getName());
                byte[] body = MonitorClientAutoConfiguration.getObjectMapper().writeValueAsBytes(devgRabbitMessage);
                channel.basicPublish(Common.exchangeName, Common.messageRouteKey, null, body);
            } else {
                logger.error("devg-monitor init rabbit-channel is null");
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage());
        }
    }

    private long getMessageNumber(String nowDay) {
        Integer dd = Integer.valueOf(nowDay);
        int i = dd % 2;
        if (i == 0) {
            return s0MessageNumber.incrementAndGet();
        } else {
            return s1MessageNumber.incrementAndGet();
        }
    }

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE - 99;
    }
}
